﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Discovery;
using McTools.Xrm.Connection;

namespace TapiConnector
{
    /// <summary>
    /// Formulaire de création et d'édition d'une connexion
    /// à un serveur Crm.
    /// </summary>
    public partial class ConnectionForm : Form
    {
        #region Variables

        /// <summary>
        /// Détail de connexion courant
        /// </summary>
        ConnectionDetail _detail;

        /// <summary>
        /// List of Crm server organizations
        /// </summary>
        List<OrganizationDetail> _organizations;

        /// <summary>
        /// Indique si l'utilisateur a demandé à se connecter
        /// au serveur
        /// </summary>
        bool _doConnect;
        bool _passwordChanged;

        #endregion

        #region Propriétés

        /// <summary>
        /// Obtient la valeur qui définit si l'utilisateur a demandé 
        /// à se connecter au serveur
        /// </summary>
        public bool DoConnect
        {
            get { return _doConnect; }
        }

        /// <summary>
        /// Obtient ou définit le détail de la connexion courante
        /// </summary>
        public ConnectionDetail CrmConnectionDetail
        {
            get { return _detail; }
            set { _detail = value; }
        }

        readonly bool _isCreationMode;

        #endregion

        #region Constructeur

        /// <summary>
        /// Créé une nouvelle instance de la classe ConnectionForm
        /// </summary>
        public ConnectionForm(ConnectionDetail detail)
        {
            InitializeComponent();
            _detail = detail;
            if (_detail == null)
            {
                _detail = new ConnectionDetail();
                _isCreationMode = true;
            }

            var tip = new ToolTip { ToolTipTitle = "Information" };
            tip.SetToolTip(tbServerName, "For CRM Online or OSDP, use:\r\ncrm.dynamics.com for North America\r\ncrm4.dynamics.com for EMEA\r\ncrm5.dynamics.com for Asia Pacific\r\n\r\nFor OnPremise:\r\nUse the server name\r\n\r\nFor IFD:\r\nUse <organization_name>.<domain>.<extension>");
            tip.SetToolTip(tbServerPort, "Specify port only if different from 80 or 443 (SSL)");
            tip.SetToolTip(tbHomeRealmUrl, "(Optional) In specific case, you should need to specify the home realm url to authenticate through ADFS");
            // allows for validation of SSL conversations
            ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
        }

        // callback used to validate the certificate in an SSL conversation
        private static bool ValidateRemoteCertificate(
        object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors policyErrors
        )
        {
            return true;
        }


        #endregion

        #region Méthodes

        protected override void OnLoad(EventArgs e)
        {
            if (_detail != null)
            {
                FillValues();
            }

            base.OnLoad(e);
        }

        private void BValidateClick(object sender, EventArgs e)
        {
            if (tbName.Text.Length == 0)
            {
                MessageBox.Show(this, "You must define a name for this connection!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (comboBoxOrganizations.Text.Length == 0 && comboBoxOrganizations.SelectedItem == null && !(cbUseIfd.Checked || cbUseOSDP.Checked))
            {
                MessageBox.Show(this, "You must select an organization!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if ((cbUseIfd.Checked || rbAuthenticationCustom.Checked) && tbUserPassword.Text.Length == 0 && _detail.UserPassword.Length == 0)
            {
                MessageBox.Show(this, "You must define a password!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (_detail == null)
                _detail = new ConnectionDetail();

            // Save connection details in structure
            _detail.ConnectionName = tbName.Text;
            _detail.IsCustomAuth = rbAuthenticationCustom.Checked;
            _detail.UseSsl = cbUseSsl.Checked;
            _detail.UseOnline = cbUseOnline.Checked;
            _detail.UseOsdp = cbUseOSDP.Checked;
            _detail.ServerName = tbServerName.Text;
            _detail.ServerPort = tbServerPort.Text;
            _detail.UserDomain = tbUserDomain.Text;
            _detail.UserName = tbUserLogin.Text;
            if (tbUserPassword.Text != "")
            {
                _detail.UserPassword = tbUserPassword.Text;
            }
            _detail.SavePassword = chkSavePassword.Checked;
            _detail.UseIfd = cbUseIfd.Checked;
            _detail.HomeRealmUrl = (tbHomeRealmUrl.Text.Length > 0 ? tbHomeRealmUrl.Text : null);

            try
            {
                FillDetails();

                if (MessageBox.Show(this, "Do you want to connect now to this server?", "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    _doConnect = true;
                }

                DialogResult = DialogResult.OK;
                Close();
            }
            catch (Exception error)
            {
                MessageBox.Show(this, error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void bCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            Close();
        }

        private void comboBoxOrganizations_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (comboBoxOrganizations.Text.Length > 0 || comboBoxOrganizations.SelectedItem != null)
            {
                bValidate.Enabled = true;
            }
            else
            {
                bValidate.Enabled = false;
            }
        }

        private void comboBoxOrganizations_TextChanged(object sender, EventArgs e)
        {
            if (comboBoxOrganizations.Text.Length > 0 || comboBoxOrganizations.SelectedItem != null)
            {
                bValidate.Enabled = true;
            }
            else
            {
                bValidate.Enabled = false;
            }
        }

        private void cbUseIfd_CheckedChanged(object sender, EventArgs e)
        {
            if (cbUseIfd.Checked)
            {
                cbUseOnline.Checked = false;
                cbUseOSDP.Checked = false;
            }

            bValidate.Enabled = cbUseIfd.Checked;

            rbAuthenticationCustom.Checked = cbUseIfd.Checked;
            rbAuthenticationIntegrated.Enabled = !cbUseIfd.Checked;
            rbAuthenticationIntegrated.Checked = !cbUseIfd.Checked;

            tbUserDomain.Enabled = cbUseIfd.Checked;
            tbUserLogin.Enabled = cbUseIfd.Checked;
            tbUserPassword.Enabled = cbUseIfd.Checked;

            tbHomeRealmUrl.Enabled = cbUseIfd.Checked;

            //bGetOrganizations.Enabled = !cbUseIfd.Checked;
            cbUseSsl.Checked = cbUseIfd.Checked;
            cbUseSsl.Enabled = !cbUseIfd.Checked;
        }

        private void cbUseOnline_CheckedChanged(object sender, EventArgs e)
        {
            if (cbUseOnline.Checked)
            {
                cbUseIfd.Checked = false;

                rbAuthenticationCustom.Checked = true;
                rbAuthenticationIntegrated.Enabled = false;
                rbAuthenticationIntegrated.Checked = false;

                tbUserDomain.Enabled = false;
                tbUserLogin.Enabled = true;
                tbUserPassword.Enabled = true;

                //bGetOrganizations.Enabled = false;
                cbUseSsl.Checked = true;
                cbUseSsl.Enabled = false;
                tbServerPort.Enabled = false;
                tbHomeRealmUrl.Enabled = false;
            }
            else
            {
                rbAuthenticationCustom.Checked = false;
                rbAuthenticationIntegrated.Enabled = true;
                rbAuthenticationIntegrated.Checked = true;

                tbUserDomain.Enabled = false;
                tbUserLogin.Enabled = false;
                tbUserPassword.Enabled = false;

                //bGetOrganizations.Enabled = true;
                cbUseSsl.Checked = false;
                cbUseSsl.Enabled = true;
                tbServerPort.Enabled = true;
            }

            //bGetOrganizations.Enabled = cbUseOnline.Checked;

        }

        private void cbUseOSDP_CheckedChanged(object sender, EventArgs e)
        {
            if (cbUseOSDP.Checked)
            {
                cbUseIfd.Checked = false;
                cbUseOnline.Checked = true;
                cbUseOnline.Enabled = false;

                rbAuthenticationCustom.Checked = true;
                rbAuthenticationIntegrated.Enabled = false;
                rbAuthenticationIntegrated.Checked = false;

                tbUserDomain.Enabled = false;
                tbUserLogin.Enabled = true;
                tbUserPassword.Enabled = true;
                tbHomeRealmUrl.Enabled = false;

                //bGetOrganizations.Enabled = false;
                cbUseSsl.Checked = true;
                cbUseSsl.Enabled = false;
                tbServerPort.Enabled = false;
            }
            else
            {
                rbAuthenticationCustom.Checked = cbUseOnline.Checked;
                rbAuthenticationIntegrated.Enabled = !cbUseOnline.Checked;
                cbUseOnline.Enabled = true;

                tbUserDomain.Enabled = false;
                tbUserLogin.Enabled = cbUseOnline.Checked;
                tbUserPassword.Enabled = cbUseOnline.Checked;

                //bGetOrganizations.Enabled = cbUseOnline.Checked; 
                cbUseSsl.Checked = cbUseOnline.Checked;
                cbUseSsl.Enabled = !cbUseOnline.Checked;
                tbServerPort.Enabled = !cbUseOnline.Checked;
            }
            //bGetOrganizations.Enabled = cbUseOSDP.Checked;

        }

        private void rbAuthenticationIntegrated_CheckedChanged(object sender, EventArgs e)
        {
            tbUserDomain.Enabled = rbAuthenticationCustom.Checked;
            tbUserLogin.Enabled = rbAuthenticationCustom.Checked;
            tbUserPassword.Enabled = rbAuthenticationCustom.Checked;
        }

        private void bGetOrganizations_Click(object sender, EventArgs e)
        {
            string warningMessage = string.Empty;
            bool goodAuthenticationData = false;
            bool goodServerData = false;

            // Check data filled by user
            if (rbAuthenticationIntegrated.Checked ||
                (
                rbAuthenticationCustom.Checked &&
                (tbUserDomain.Text.Length > 0 || cbUseIfd.Checked || cbUseOSDP.Checked) &&
                tbUserLogin.Text.Length > 0 &&
                (tbUserPassword.Text.Length > 0 || _detail.UserPassword.Length > 0)
                )
                ||
                    (cbUseOnline.Checked && !string.IsNullOrEmpty(tbUserLogin.Text) &&
                    (!string.IsNullOrEmpty(tbUserPassword.Text) || !string.IsNullOrEmpty(_detail.UserPassword))) )
                goodAuthenticationData = true;

            if (tbServerName.Text.Length > 0)
                goodServerData = true;

            if (!goodServerData)
            {
                warningMessage += "Please provide server name\r\n";
            }
            if (!goodAuthenticationData)
            {
                warningMessage += "Please fill all authentication textboxes\r\n";
            }

            if (warningMessage.Length > 0)
            {
                MessageBox.Show(this, warningMessage, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else
            {
                // Save connection details in structure
                if (_isCreationMode)
                {
                    _detail = new ConnectionDetail();
                }

                _detail.ConnectionName = tbName.Text;
                _detail.IsCustomAuth = rbAuthenticationCustom.Checked;
                _detail.UseSsl = cbUseSsl.Checked;
                _detail.ServerName = tbServerName.Text;
                _detail.ServerPort = tbServerPort.Text;
                _detail.UserDomain = tbUserDomain.Text;
                _detail.UserName = tbUserLogin.Text;
                if (tbUserPassword.Text != "")
                {
                    _detail.UserPassword = tbUserPassword.Text;
                }
                _detail.UseIfd = cbUseIfd.Checked;
                _detail.UseOnline = cbUseOnline.Checked;
                _detail.UseOsdp = cbUseOSDP.Checked;
                _detail.HomeRealmUrl = (tbHomeRealmUrl.Text.Length > 0 ? tbHomeRealmUrl.Text : null);

                _detail.AuthType = AuthenticationProviderType.ActiveDirectory;
                if (cbUseIfd.Checked)
                {
                    _detail.AuthType = AuthenticationProviderType.Federation;
                }
                else if (cbUseOSDP.Checked)
                {
                    _detail.AuthType = AuthenticationProviderType.OnlineFederation;
                }
                else if (cbUseOnline.Checked)
                {
                    _detail.AuthType = AuthenticationProviderType.LiveId;
                }

                // Launch organization retrieval
                comboBoxOrganizations.Items.Clear();
                Thread retrieveOrgThread = new Thread(new ThreadStart(FillOrganizations));
                retrieveOrgThread.Start();
            }
        }

        /// <summary>
        /// Method to fill organization combobox
        /// </summary>
        public void FillOrganizations()
        {
            try
            {
                Guid applicationId = new Guid("6f4cad4a-e3d4-41d7-8ac7-cd17a69c3997");// Guid.NewGuid();//

                SetCursor(this, Cursors.WaitCursor);

                ServerConnection sc = new ServerConnection();

                sc.config = sc.GetServerConfiguration(_detail, applicationId);

                //if (ConnectionList != null && ConnectionList.UseCustomProxy)
                //{
                //    WebProxy proxy = new WebProxy(ConnectionList.ProxyAddress + ":" + ConnectionList.ProxyPort, true);
                //    proxy.Credentials = new NetworkCredential(ConnectionList.UserName, ConnectionList.Password);
                //    System.Net.HttpWebRequest.DefaultWebProxy = proxy;
                //}
                //else
                //{
                    System.Net.HttpWebRequest.DefaultWebProxy = WebProxy.GetDefaultProxy();
                //}

                using (DiscoveryServiceProxy serviceProxy = new DiscoveryServiceProxy(sc.config.DiscoveryUri, sc.config.HomeRealmUri, sc.config.Credentials, sc.config.DeviceCredentials))
                {
                    // Obtain organization information from the Discovery service. 
                    if (serviceProxy != null)
                    {
                        _organizations = new List<OrganizationDetail>();

                        // Obtain information about the organizations that the system user belongs to.
                        OrganizationDetailCollection orgs = sc.DiscoverOrganizations(serviceProxy);

                        foreach (OrganizationDetail orgDetail in orgs)
                        {
                            _organizations.Add(orgDetail);

                            AddItem(comboBoxOrganizations, new Organization() { OrganizationDetail = orgDetail });

                            SelectUniqueValue(comboBoxOrganizations);
                        }
                    }
                }

                SetEnableState(comboBoxOrganizations, true);
            }
            catch (Exception error)
            {
                string errorMessage = CrmExceptionHelper.GetErrorMessage(error, false);

                if (error.InnerException != null)
                    DisplayMessageBox(this, "Error while retrieving organizations : " + errorMessage + "\r\n" + error.InnerException.Message + "\r\n" + error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                else
                    DisplayMessageBox(this, "Error while retrieving organizations : " + errorMessage + "\r\n" + error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                SetCursor(this, Cursors.Default);
            }
        }

        /// <summary>
        /// Remplit les contrôles du formulaire avec les données
        /// du détail de connexion courant
        /// </summary>
        private void FillValues()
        {
            rbAuthenticationCustom.Checked = _detail.IsCustomAuth;
            rbAuthenticationIntegrated.Checked = !_detail.IsCustomAuth;

            //rbAuthenticationIntegrated.CheckedChanged += new EventHandler(rbAuthenticationIntegrated_CheckedChanged);

            tbName.Text = _detail.ConnectionName;
            tbServerName.Text = _detail.ServerName;
            tbServerPort.Text = _detail.ServerPort;
            tbUserDomain.Text = _detail.UserDomain;
            tbUserLogin.Text = _detail.UserName;
            tbUserPassword.Text = "";
            chkSavePassword.Checked = _detail.SavePassword;
            comboBoxOrganizations.Text = _detail.OrganizationFriendlyName;
            cbUseIfd.Checked = _detail.UseIfd;
            cbUseOSDP.Checked = _detail.UseOsdp;
            cbUseOnline.Checked = _detail.UseOnline;
            cbUseSsl.Checked = _detail.UseSsl;
            tbHomeRealmUrl.Text = _detail.HomeRealmUrl;

            tbHomeRealmUrl.Enabled = _detail.UseIfd;
        }

        /// <summary>
        /// Remplit le détail de connexion avec le contenu 
        /// des contrôles du formulaire
        /// </summary>
        /// <returns></returns>
        private void FillDetails()
        {
            bool hasFoundOrg = false;

            OrganizationDetail selectedOrganization = comboBoxOrganizations.SelectedItem != null ? ((Organization)comboBoxOrganizations.SelectedItem).OrganizationDetail : null;

            if (_organizations == null || _organizations.Count == 0)
            {
                FillOrganizations();
            }

            foreach (OrganizationDetail organization in _organizations)
            {
                if (organization.UniqueName == selectedOrganization.UniqueName)
                {
                    _detail.OrganizationServiceUrl = organization.Endpoints[EndpointType.OrganizationService];
                    _detail.Organization = organization.UniqueName;
                    _detail.OrganizationFriendlyName = organization.FriendlyName;

                    _detail.ConnectionName = tbName.Text;

                    if (_isCreationMode)
                    {
                        _detail.ConnectionId = Guid.NewGuid();
                    }

                    hasFoundOrg = true;

                    break;
                }
            }

            if (!hasFoundOrg)
            {
                throw new Exception("Unable to match selected organization with list of organizations in this deployment");
            }
        }

        #endregion

        #region Méthodes déléguées

        public static void AddItem(ComboBox combobox, object item)
        {
            MethodInvoker miAddItem = delegate
            {
                combobox.Items.Add(item);
            };

            if (combobox.InvokeRequired)
            {
                combobox.Invoke(miAddItem);
            }
            else
            {
                miAddItem();
            }
        }

        public static void SelectUniqueValue(ComboBox combobox)
        {
            MethodInvoker miSelectUniqueValue = delegate
            {
                combobox.SelectedIndex = 0;
            };

            if (combobox.InvokeRequired)
            {
                combobox.Invoke(miSelectUniqueValue);
            }
            else
            {
                miSelectUniqueValue();
            }
        }

        internal static void SetEnableState(Control control, bool enabled)
        {
            MethodInvoker miSetEnableState = delegate
            {
                control.Enabled = enabled;
            };

            if (control.InvokeRequired)
            {
                control.Invoke(miSetEnableState);
            }
            else
            {
                miSetEnableState();
            }
        }

        public static void SetCursor(Control control, Cursor cursor)
        {
            MethodInvoker miSetCursor = delegate
            {
                control.Cursor = cursor;
            };

            if (control.InvokeRequired)
            {
                control.Invoke(miSetCursor);
            }
            else
            {
                miSetCursor();
            }
        }

        public static DialogResult DisplayMessageBox(Form form, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
        {
            DialogResult result = DialogResult.Ignore;

            MethodInvoker DisplayMessage = delegate
            {
                result = MessageBox.Show(form, message, caption, buttons, icon);
            };

            if (form.InvokeRequired)
            {
                form.Invoke(DisplayMessage);
                return result;
            }
            else
            {
                DisplayMessage();
                return result;
            }
        }

        #endregion
    }
}